package com;

import com.object.*;
import com.util.CRC16;
import com.util.MyUtil;
import com.util.ProtocolUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.ConcurrentHashMap;



/*
服务器发送分包数据-》
  7F F7      CA           { F3             CA CA              [00 01      DATA            FF FA   ]}  FA
--|帧头|--|帧长度|--|帧类型：分包发送|--|剩余数据总长度| --|包序号|--|数据196Bytes|--|CRC16校验|--|XOR|
定义：
[] 中为分包数据段, 即分包数据段总长度
{} 为除去帧头帧尾的长度，即数据帧总长度


服务器发送分包指令-》
  7F F7      0A          F2                  20              00 03 20 00     08 2A      xx        50
--|帧头|--|帧长度|--|帧类型：分包请求|--|数据帧类型：升级|--| 数据长度 |----|包数|--|附加数据|-- |XOR|
 */
public class OtaService {
    public  volatile static int UNPACK_FRAME_LENGTH=1024; // 整个分包数据段总长度
  

	private  static int UNPACK_FRAME_EXPAND_LENGTH_LIMIT = 253; // 分包数据扩展长度字节的阈值
    public  static int HEADER_TAIL_LENGTH = 4; // 分包数据段的头尾长度，包含：包序号，CRC校验
    public volatile static int DATA_FRAME_LENGTH = UNPACK_FRAME_LENGTH - HEADER_TAIL_LENGTH; // 数据段中包含升级程序的长度
    public volatile static int FRAME_LENGTH = UNPACK_FRAME_LENGTH + 3;
    public  static int UPGRADE_HOST_LENGTH = 11; // 升级主机的帧长度,从帧类型到附加数据结束
    public volatile static int UPGRADE_SLAVE_LENGTH = UPGRADE_HOST_LENGTH + 3;
    private  static int BLE_SLAVE_LIMIT = 24;
    private  static int RETRY_TIMES_LIMIT = 5;
    private  static int EXCLUDE_DATA_LENGTH = 7; // 不包含数据字段的长度，即帧类型1，剩余数据总长度2，包序号2，CRC校验2，这些字段的长度和

    private String gprsId;
    private Gprs gprs;
    private ChannelHandlerContext ctx;
    private int fileLength; // 升级文件的总长度
    private byte[][] fileBuffers;
    private static ConcurrentHashMap<String, byte[][]> fileHashMap = new ConcurrentHashMap<>();
    private static ConcurrentHashMap<String, Integer> fileLengthMap = new ConcurrentHashMap<String, Integer>();
    private OtaObject otaObject;
    private int packIndex; // 升级包序号
    private int retridTimes; // 重发次数
    private boolean fileSendDone; // 升级文件是否发送完毕


    private OtaService(String id, Gprs gprs){
        this.gprsId = id;
        this.gprs = gprs;
        this.ctx = gprs.getChannelHandlerContext();
    };

    public static  OtaService create(Gprs gprs){
        return new OtaService(gprs.getId(), gprs);
    }

    public OtaService setBinFile(String path) throws Exception{
        File file = new File(path);
        if (file.exists() && !file.isDirectory()){
            loadBinFile(path);
            return this;
        }
        else
            throw file.exists()?new IllegalArgumentException("无效的文件路径: 不能为文件夹"): new IllegalArgumentException("无效的文件路径: 文件不存在");
    }

    public OtaService setOtaObject(OtaObject ota) throws  Exception{
        this.otaObject = ota;
        setBinFile(otaObject.binFilePath);
        return this;
    }

    public OtaObject getOtaObject() {
        return otaObject;
    }

    /**
     * @return 升级文件的总字节长度
     */
    public int getBinFileLength(){
        return fileLength;
    }

    /**
     * @return 总的分包个数
     */
    public int getPackCount(){
        return fileBuffers.length;
    }



    // 发送升级分包请求
    public void startUpgrade() throws  Exception{
        if (otaObject == null)
            throw new IllegalAccessException("OtaObject need to set before start");
        else{
            resetService();
            sendUnpackRequest();
        }
    }

    // 收到分包请求的应答
    public void getUnpackRequestReply(byte status){
        PKT_RQ_Status ackStatus = PKT_RQ_Status.checkStatus(status);
        if (ackStatus == PKT_RQ_Status.H80 && packIndex == 0){  // 允许发送
                sendNextUpgradeFile();
        }
        else{  // 记录错误原因
//            TODO: 请求失败，记录失败原因，重新发送请求or直接终止升级流程

            upgradeFailed(ackStatus);
        }
    }


    public void getPackFrameReply(int replyIndex, byte status){
        PKT_Status ackStatus = PKT_Status.checkStatus(status);
        if (ackStatus == PKT_Status.H80 && replyIndex == packIndex){
            sendNextUpgradeFile();
        }
        else{ // 返回失败原因，并重新尝试
            resendUpgradeFile();
            if (retridTimes >= RETRY_TIMES_LIMIT)
                upgradeFailed(ackStatus);
        }
    }

    public void getUpgradeResult(byte[] reply){
        OtaTarget target = OtaTarget.checkTarget(reply[0]);
        if (target == OtaTarget.UNKNOWN){
            // TODO: 未知升级目标
            upgradeFailed(target);
        }
        else if (fileSendDone &&target == OtaTarget.BLE_SLAVE) { // 从机
            // TODO: 从机成功升级
            OtaResult[] results = new OtaResult[ProtocolUtil.SLAVE_NUMBER];
            int index = 1;
            while(index < reply.length && index <= ProtocolUtil.SLAVE_NUMBER ){
                byte re = reply[index];
                OtaResult result = OtaResult.checkResult(re);
                results[index-1] =result;
                ++index;
            }
            upgradeSuccess(results);
        }
        else if (fileSendDone && (target == OtaTarget.BLE_HOST || target == OtaTarget.STM_HOST)){ // 主机
            byte status = reply[1];
            OtaResult result = OtaResult.checkResult(status);
            if (result == OtaResult.SUCCESS){
                upgradeSuccess(result);
            }
            else{
                upgradeFailed(result);
            }
        }
        else{
            upgradeFailed("升级数据包未完全发送");
        }

    }

    //region private method

    private void upgradeFailed(Object ob){
        // TODO: 升级失败的处理
        String info = "";
        if (ob instanceof PKT_RQ_Status){
            info = ("升级请求错误: " + ((PKT_RQ_Status) ob).getStatusInfo());
        }
        else if (ob instanceof  PKT_Status){
            info = ("已发送" + packIndex + "/" + getPackCount() + "  错误信息:" + ((PKT_Status) ob).getStatusInfo());

        }
        else if (ob instanceof  OtaTarget){
            info = ((OtaTarget) ob).getInfo();

        }
        else if (ob instanceof OtaResult){
            info = ("主机升级结果:"+((OtaResult) ob).getInfo());
        }
        this.gprs.otaFailed(info);

        // TODO: test
        System.out.println("fail");
    }

    private void upgradeSuccess(OtaResult[] ob){
        String info = new String();
        for(int i = 0 ;i < ob.length; ++i){
            info += "#" + (i + 1) + ": " + ob[i].getInfo() + " ";
        }
        doUpgradeSuccess(info);
    }

    private void upgradeSuccess(OtaResult ob){
        doUpgradeSuccess(ob.getInfo());

    }
    private void doUpgradeSuccess(String info){
        this.gprs.otaSuccessed(info);

    }

    private  void resetService(){
        packIndex = 0;
        fileSendDone = false;
    }


    private void sendNextUpgradeFile(){
        retridTimes = 0;
        if (packIndex < fileBuffers.length) {
            sendMsg(fileBuffers[packIndex]);
            ++packIndex;
        }
        else{
            fileSendDone = true;
        }
    }

    private void resendUpgradeFile(){
        if (packIndex > 0 && retridTimes < RETRY_TIMES_LIMIT){
            sendMsg(fileBuffers[packIndex - 1]);
            ++retridTimes;
        }
    }

    private void sendMsg(byte[] msg){
        ByteBuf out = Unpooled.copiedBuffer(msg);
        this.ctx.writeAndFlush(out);

    }

    private void sendUnpackRequest() throws Exception{
        ByteBuf out = Unpooled.buffer(UPGRADE_SLAVE_LENGTH);
        out.writeByte(ProtocolUtil.PKT_RQ);
        out.writeByte(ProtocolUtil.UPGRADE);
        out.writeInt(this.getBinFileLength());
        out.writeShort(this.getPackCount());

        if (otaObject.otaTarget == OtaTarget.STM_HOST)
            out.writeByte(otaObject.otaTarget.getValue());
        else if(otaObject.otaTarget == OtaTarget.BLE_HOST)
            out.writeByte(otaObject.otaTarget.getValue());
        else
            out.writeByte(otaObject.otaTarget.getValue());

        out.writeByte(otaObject.firmware);
        out.writeByte(otaObject.version);
        if (otaObject.otaTarget == OtaTarget.BLE_SLAVE) {
            if (otaObject.slaveIndex.length == 0)
                throw new IllegalArgumentException("upgrade ble slave operation must specify slave index");
            else {
                byte[] res = getUpgradeBleSlaveIndexBytes(otaObject.slaveIndex);
                out.writeBytes(res);
            }
        }

        this.ctx.writeAndFlush(out);

    }

    private byte[] getUpgradeBleSlaveIndexBytes(int[] indexes) throws Exception{
        byte[] result = new byte[3];
        for(int i = 0; i < indexes.length; ++i){
            if (indexes[i] > BLE_SLAVE_LIMIT || indexes[i] <1)
                throw new IllegalArgumentException("slave index must be in [1,24]");
            else{
                int res= (indexes[i] - 1) / 8;
                result[res] = MyUtil.setBitAt(result[res], indexes[i] - 8 * res);
            }
        }
        return result;
    }





    private void loadBinFile(String path) throws Exception{
        if (fileHashMap.containsKey(path)) {
            fileBuffers = fileHashMap.get(path);
            fileLength = fileLengthMap.get(path);
        }
        else{
            Path filePath = Paths.get(path);
            byte[] fileBytes = Files.readAllBytes(filePath);
            fileLength = fileBytes.length;
            ByteBuf buf = Unpooled.wrappedBuffer(fileBytes); // avoid memory copy
            fileBuffers = unpackFrameToBufs(buf);
            fileHashMap.put(path, fileBuffers);
            fileLengthMap.put(path, fileLength);
        }
    }

    private byte[][] unpackFrameToBufs(ByteBuf buf){
        int packNum = buf.readableBytes() / DATA_FRAME_LENGTH;
        if (packNum * DATA_FRAME_LENGTH < buf.readableBytes())
            ++packNum;
        byte[][] ret = new byte[packNum][];
        for(int i = 0; i < packNum; ++i){
           byte[] readIn = readBytesFromBuf(buf);
           ret[i] = getOneFrame(readIn, i + 1);
        }
        return ret;
    }

    private byte[] readBytesFromBuf(ByteBuf buf){
        byte[] ret;
        if (buf.readableBytes() >= DATA_FRAME_LENGTH){
            ret = new byte[DATA_FRAME_LENGTH];
            buf.readBytes(ret);
        }
        else{
            ret = new byte[buf.readableBytes()];
            buf.readBytes(ret);
        }
        return ret;
    }

    /**
     * @param in
     * @param index
     * @return ByteBuf 从帧类型(包含在内)开始到最后的所有数据
     */
    private byte[] getOneFrame(byte[] in, int index){
        // 需要将帧类型包含在内，长度 + 1
        ByteBuf buf;
        if (in.length == DATA_FRAME_LENGTH){
           buf = Unpooled.buffer(FRAME_LENGTH);
        }
        else if(in.length + HEADER_TAIL_LENGTH > UNPACK_FRAME_EXPAND_LENGTH_LIMIT){
           buf = Unpooled.buffer(in.length + EXCLUDE_DATA_LENGTH);
        }else {
        	buf = Unpooled.buffer(in.length + EXCLUDE_DATA_LENGTH-2);
        }
        buf.writeByte(ProtocolUtil.PKT);

        // 数据段首位为两个字节的长度：从此长度位之后到整个帧结束的长度(包含末位的异或校验)
        // 如果数据段长度
        if (in.length + HEADER_TAIL_LENGTH > UNPACK_FRAME_EXPAND_LENGTH_LIMIT)
            buf.writeShort(in.length + HEADER_TAIL_LENGTH + 1);

        byte[] indexByte = MyUtil.intToUInt16Bytes(index);
        buf.writeBytes(indexByte);
        buf.writeBytes(in);
        buf.writeBytes(CRC16.calculate(MyUtil.byteArrayConcat(indexByte, in))); // CRC 校验包含包序号帧
        return buf.array();
    }
    //endregion


    // test
    int index = 0;
    public byte[] readNextBuf(){
        if (index < fileBuffers.length)
            return fileBuffers[index++];
        else
            return null;
    }


}
